/*
 * Copyright 2007 Tiberiumlabs
 *
 * This file is part of Tiberiumlabs Shards.
 *
 * Tiberiumlabs Shards is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tiberiumlabs Shards is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.tiberiumlabs.shards.toolkit;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author <a href="paranoid.tiberiumlabs@gmail.com">Paranoid</a>
 */
public abstract class BackgroundWorker<E> {

    /**
     * Override this method to perform some task with each element you submitted.<br/>
     * Parameter is submitted here in order they were added to method.<br/>
     * If exceptions will occur - it will not break all work, but just one element will be lost.<br/>
     */
    public abstract void doInBackground(E e);

    private int priority = Thread.NORM_PRIORITY-1;

    /**
     * @return this worker priority
     */
    public final int getPriority() {
        return priority;
    }

    /**
     * Changes priority of this worker.<br/>
     * Default priority is {@code Thread.NORM_PRIORITY - 1}.<br/>
     * Priority can be changed only before worker starts.
     * @param priority priority to set for this worker
     */
    public final void setPriority(int priority) {
        this.priority = priority;
    }

    private boolean daemon = true;

    public final void setDaemon(boolean daemon) {
        this.daemon = daemon;
    }

    public final boolean isDaemon() {
        return daemon;
    }
    
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    

    private final LinkedList<E> tasksList = new LinkedList<E>();

    /**
     * Executes {@code doInBackground} method on this element in the future.<br/>
     * Excecution will be in same order as elements were added.<br/>
     * If worker thread is not already working - it will be created and started.<br/>
     * If worker is already created - this task will pass to {@code doInBackground}
     * after all current tasks will be completed.<br/>
     * Once task is submitted to worker - it can be removed only by {@code purge} method.<br/>
     */
    public final void add(E e) {
        if (shutdown) {
            return;
        }
        synchronized (tasksList) {
            tasksList.addLast(e);
        }
        notifyWorker();
    }

    /**
     * Executes {@code doInBackground} method on this elements in the future.<br/>
     * Excecution will be in same order as elements were added.<br/>
     * If worker thread is not already working - it will be created and started.<br/>
     * If worker is already created - this tasks will pass to {@code doInBackground}
     * after all current tasks will be completed.<br/>
     */
    public final void addAll(Collection<? extends E> collection) {
        if (shutdown) {
            return;
        }
        synchronized (tasksList) {
            tasksList.addAll(collection);
        }
        notifyWorker();
    }

    /**
     * Clears current tasks list, so worker will stop after current iteration is done.
     */
    public final void purge() {
        synchronized (tasksList) {
            tasksList.clear();
        }
    }

    /**
     * Method to start worker thread.<br/>
     * Worker is started as daemon with specified (or default) priority.<br/>
     * Default priority is {@code Thread.NORM_PRIORITY-1}.<br/>
     */
    public final void start() {
        this.worker = new Thread() {
            @Override
            public void run() {
                process();
            }
        };
        this.worker.setDaemon(daemon);
        this.worker.setPriority(priority);
        this.worker.start();
    }
    
    private boolean shutdown = false;
    public final void shutdown() {
        this.shutdown = true;
        notifyWorker();
    }
    
    public final void shutdownAndWait() {
        shutdown();
        if (worker != null) {
            try {
                worker.join();
            } catch (InterruptedException ex) {
            }
        }
    }
    
    public final List<E> shutdownNow() {
        synchronized (tasksList) {
            shutdown = true;
            List<E> result = new ArrayList<E>(tasksList.size());
            Iterator<E> taskIterator = tasksList.iterator();
            while (taskIterator.hasNext()) {
                result.add(taskIterator.next());
            }
            tasksList.clear();
            notifyWorker();
            worker.interrupt();
            return result;
        }
    }

    /**
     * Returns current tasks list. This list is unmodifiable, so you
     * can't add or remove tasks from it.
     * @return current tasks list.
     */
    public final List<? extends E> getTasksList() {
        return tasksList;
    }

    private Thread worker;

    private final void notifyWorker() {
        synchronized (this) {
            notify();
        }
    }

    private final void process() {
        while (true) {
            if (tasksList.isEmpty()) {
                if (shutdown) {
                    return;
                }
                try {
                    synchronized (this) {
                        wait();
                    }
                } catch (InterruptedException ignore) {
                }
            }
            while (tasksList.size() > 0) {
                E e;
                synchronized (tasksList) {
                    e = tasksList.removeFirst();
                }
                if (e != null) {
                    try {
                        doInBackground(e);
                    } catch (Throwable th) {
                        System.err.println("Exception with element: " + e + "; " + th.getClass() + ": " + th.getMessage());
                        th.printStackTrace();
                    }
                }
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        List<E> tasks = shutdownNow();
        StringBuilder sb = new StringBuilder();
        sb.append("WARNING: There are some uncompleted tasks in ");
        if (name == null) {
            name = this.getClass().getName();
        }
        sb.append(name);
        sb.append(':').append('\n');
        for (int i = 0; i < tasks.size(); i++) {
            sb.append(tasks.get(i));
            sb.append('\n');
        }
        System.err.println(sb.toString());
        super.finalize();
    }

}
